package net.qiqbframework.eventhandling;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.qiqbframework.config.ConfigurationException;
import net.qiqbframework.messaging.monitoring.MessageMonitor;
import net.qiqbframework.messaging.monitoring.NoOpMessageMonitor;
import net.qiqbframework.messaging.task.RollbackConfiguration;
import net.qiqbframework.tracing.NoOpSpanFactory;
import net.qiqbframework.tracing.SpanFactory;

import javax.annotation.Nonnull;
import java.util.Objects;

import static net.qiqbframework.common.BuilderUtils.assertNonNull;
import static net.qiqbframework.common.BuilderUtils.assertThat;

@Slf4j
@Getter
public abstract class AbstractEventProcessorBuilder {

    protected String name;
    private EventMessageHandler eventHandlerInvoker;
    private RollbackConfiguration rollbackConfiguration;
    private ErrorHandler errorHandler = PropagatingErrorHandler.INSTANCE;
    private MessageMonitor<? super EventMessage<?>> messageMonitor = NoOpMessageMonitor.INSTANCE;
    private EventProcessorSpanFactory spanFactory = DefaultEventProcessorSpanFactory.builder()
            .spanFactory(NoOpSpanFactory.INSTANCE)
            .build();


    public AbstractEventProcessorBuilder name(@Nonnull String name) {
        assertEventProcessorName(name, "The EventProcessor name may not be null or empty");
        this.name = name;
        return this;
    }


    public AbstractEventProcessorBuilder eventHandlerInvoker(@Nonnull EventMessageHandler eventHandlerInvoker) {
        assertNonNull(eventHandlerInvoker, "EventHandlerInvoker may not be null");
        this.eventHandlerInvoker = eventHandlerInvoker;
        return this;
    }


    public AbstractEventProcessorBuilder rollbackConfiguration(@Nonnull RollbackConfiguration rollbackConfiguration) {
        assertNonNull(rollbackConfiguration, "RollbackConfiguration may not be null");
        this.rollbackConfiguration = rollbackConfiguration;
        return this;
    }


    public AbstractEventProcessorBuilder errorHandler(@Nonnull ErrorHandler errorHandler) {
        assertNonNull(errorHandler, "ErrorHandler may not be null");
        this.errorHandler = errorHandler;
        return this;
    }

    /**
     * Sets the {@link MessageMonitor} to monitor {@link EventMessage}s before and after they're processed. Defaults
     * to a {@link NoOpMessageMonitor}.
     *
     * @param messageMonitor a {@link MessageMonitor} to monitor {@link EventMessage}s before and after they're
     *                       processed
     * @return the current Builder instance, for fluent interfacing
     */
    public AbstractEventProcessorBuilder messageMonitor(@Nonnull MessageMonitor<? super EventMessage<?>> messageMonitor) {
        assertNonNull(messageMonitor, "MessageMonitor may not be null");
        this.messageMonitor = messageMonitor;
        return this;
    }


    /**
     * Sets the {@link EventProcessorSpanFactory} implementation to use for providing tracing capabilities. Defaults
     * to a {@link DefaultEventProcessorSpanFactory} backed by a {@link NoOpSpanFactory} by default, which provides
     * no tracing capabilities.
     *
     * @param spanFactory The {@link SpanFactory} implementation
     * @return The current Builder instance, for fluent interfacing.
     */
    public AbstractEventProcessorBuilder spanFactory(@Nonnull EventProcessorSpanFactory spanFactory) {
        assertNonNull(spanFactory, "SpanFactory may not be null");
        this.spanFactory = spanFactory;
        return this;
    }

    protected void validate() throws ConfigurationException {
        assertEventProcessorName(name, "The EventProcessor name is a hard requirement and should be provided");
        assertNonNull(eventHandlerInvoker, "The EventHandlerInvoker is a hard requirement and should be provided");
        assertNonNull(rollbackConfiguration,
                "The RollbackConfiguration is a hard requirement and should be provided");
    }

    private void assertEventProcessorName(String eventProcessorName, String exceptionMessage) {
        assertThat(eventProcessorName, name -> Objects.nonNull(name) && !"".equals(name), exceptionMessage);
    }
}
